谷歌提出治理开源软件漏洞的新框架:知悉、预防、修复
编译:奇安信代码卫士团队
虽然开源软件安全引起行业重视无可厚非,但相应的解决方案要求我们就执行过程中存在的机遇和挑战达成共识。这个问题纷繁复杂,涉及面广,包括供应链、依赖关系管理、身份和构建管道。问题框架化得越好,解决方案给出得越快;我们在此提出“知悉、预防、修复”框架,旨在说明行业应当如何思考开源漏洞以及首先应当解决哪些具体漏洞的问题。该框架的主要内容包括:
就元数据和身份标准达成共识:行业需要就基本原则达成共识,才能解决这些复杂问题。在元数据详情和身份方面获得共识将赋能自动化、减少软件更新所需投入,并最小化漏洞影响。
提高透明度并审查关键软件:对于安全性至关重要的软件,我们需要就开发流程达成一致意见,确保流程得到充分审查、避免单方面做出更改,并且以清晰明了的方式促成明确完善且可验证的官方版本发布。
如下框架和目标旨在引发整个行业对开源软件安全的讨论,并取得进展。
最近发生的多起事件,让软件界更深刻地了解到供应链攻击带来的真实风险。开源软件的安全风险应该更小,因为所有的代码和依赖关系都公开可查可验证。虽然一般而言确实如此,但前提是人们确实这么做了。由于依赖关系如此之多,因此监控所有依赖关系并不可行,而且很多开源包并未得到妥善维护。
一款程序常常直接或间接地依赖于数千个软件包和库。例如,Kubernetes 当前依赖的程序包数量约为1000个。和闭源软件相比,开源软件使用的依赖关系的数量更庞大且源自更多的提供商;需要得到信任的唯一实体的数量可能非常多。这就导致我们极其难以了解开源软件如何用于产品中以及相关漏洞是什么,而且我们也无法保证所构建的软件和源代码匹配。
退一步讲,尽管供应链攻击带来风险,但大多数漏洞的危害性并不大也并非有意为之,而是意图良好的开发人员的无心之过。此外,恶意人员更可能利用已知漏洞而非自己挖掘漏洞,原因无非是前者更容易。因此,我们必须集中力量做出根本改变,解决大多数漏洞,如此也将推动整个行业对复杂问题的解决,如解决供应链攻击。
为数不多的组织机构能够验证自身使用的所有程序包是否安全,遑论这些程序包的更新。当前,追踪这些程序包牵涉大量基础设施并需要大量人力投入。虽然谷歌具备这些资源并在管理开源包方面走得更远(如将内部使用的所有开源包纳入私有库中),但追踪这些更新时仍感到力不从心。更新流本身就足以让人望而生畏。任何解决方案的核心将会是更高的自动化,而这也将成为我们在2021年及未来开展开源安全工作的关键主题。
鉴于这是需要整个行业进行合作的复杂问题,因此我们的目的是围绕具体目标进行探讨。虽然谷歌因联合创立了OpenSSF 机构而成为这次协作的焦点,但要获得进展,我们需要整个行业的参与,需要整个行业就问题是什么以及如何解决达成一致意见。在此,我们抛砖引玉,提供了将问题框架化的一种方法以及一些具体目标,希望能够促进行业范围内的探讨。
我们建议将挑战框架化为相互之间基本独立的三个方面,每个方面都具有具体的目标:
1、知悉软件中的漏洞
2、预防新漏洞的产生,以及
3、修复或消除漏洞
改进开发流程的安全性是一个相关但独立的问题,对于供应链安全至关重要。我们列出这一问题所带来的挑战,并在本文的第四节“关键软件的预防措施”提出了相关目标。
知悉漏洞要比想象得难得多,原因很多。尽管有很多漏洞报告机制,但我们仍然难以了解漏洞所影响的具体软件版本。
目标:精确的漏洞数据
首先,从所有可用数据来源捕获精确的漏洞元数据至关重要。例如,了解哪个具体版本引入漏洞有助于判断所用软件是否受影响,了解漏洞何时修复可使打补丁过程准确及时(潜在利用的窗口期也会缩短)。在理想情况下,这种诊断工作流应该是自动化完成。
第二,大部分漏洞位于依赖关系中而非所编写或直接控制的代码中。因此,即使你并未改变代码,漏洞数量也会川流不息:修复某些漏洞而引入其它漏洞。
目标:漏洞数据库的标准架构
我们需要具备追踪和维护开源漏洞、了解漏洞后果并管理缓解措施的基础设施和行业标准。标准的漏洞架构可允许在多个漏洞数据库中使用常用工具并简化追踪任务,尤其是在漏洞牵涉多种语言或子系统的情况下更是如此。
目标:准确追踪依赖关系
更好的工具集能使我们快速地了解新发现漏洞所影响的软件,而且这个问题因为大型依赖关系树的规模和其动态变化的性质而越发难以解决。由于用于版本解析的软件仅可通过安装程序获取,因此当前的实践常常使我们难以在不进行实际安装的情况下判断出受漏洞影响的版本。
在理想情况下,我们在漏洞出现之时就将其阻止,不过尽管测试和分析工具有助于我们阻止漏洞,但漏洞防御将一直是个老大难问题。我们关注两个具体方面:
使用前,了解新依赖关系的风险
改进关键软件的开发流程
目标:了解新依赖关系的风险
第一类主要是在决定使用程序包之时,了解其中的漏洞。使用新依赖关系具有内在风险,需要做出知情决策。一旦使用了依赖关系,那么一般而言想要消除就变得更加困难。
虽然通过了解漏洞开了个好头,但我们能够做得还有很多。
很多漏洞是因为未遵循软件开发流程中的安全最佳实践造成的。所有的贡献者都使用双因素认证机制了吗?项目是否具有持续集成设置并进行了测试?是否集成了模糊测试?这类安全检查有助于客户了解在使用新依赖关系时面临的风险。在这些检查中得分低的程序包需要我们开展更严密的审查并做出修复计划。
OpenSSF 最近推出的“安全积分卡 (Security Scorecards)”项目试图以完全自动化的方式生成这些数据点。同时积分卡有助于防御普遍存在的”误植域名 (typosquatting) 攻击“,因为这类攻击的得分更低且在很多安全检查中不合格。
虽然改进关键软件的开发流程和漏洞防御有关,但值得本文后续专门探讨。
虽然漏洞修复的问题不在我们的讨论范围之内,但在管理软件依赖关系漏洞的具体问题方面,我们能做的还有很多。虽然目前尚不存在助力工具,但随着我们在精确度方面的改进,未来投入新流程和工具集将物有所值。
当然,我们可以选择直接修复漏洞。如果可以以向后兼容的方式修复漏洞,那么人人皆可使用该修复方案。但问题是,我们可能并非该问题的专家,或者无力直接做出修改。修复漏洞的假设是软件维护人员也意识到问题的存在,而且具备披露漏洞的知识和资源。
相反,如果我们只是删除包含该漏洞的依赖关系,那么只是为我们自己以及导入或使用该软件的用户修复了这个漏洞,而并没有为其他人修复。这种变化是我们能够直接控制的。
虽然这些场景代表了软件和漏洞之间依赖关系链的两个端点,但在实践中可能存在很多介于二者之间的程序包。我们希望依赖关系所涉及的人员能够修复漏洞。遗憾的是,仅修复一个环节是不够的:你和漏洞之间的依赖关系链的每个链条都需要在修复软件之前得到更新。每个链条必须包括它之下的已修复版本才能修复漏洞。如此,需要开展自下而上的更新,除非你能够消除整个依赖关系,而这样做要求具备类似的特征且极少成功——不过在特定情境下就是最好的解决方案。
目标:了解消除漏洞的选项
当前这个流程是不清晰的:其他人做出了哪些改进以及应该在哪个级别应用何种升级?流程卡在哪里?谁负责修复漏洞本身?谁负责传播修复方案?
目标:加速修复的通知系统
最终,你的依赖关系得以修复,你可以在本地升级到新版本。了解何时会产生这种结果是一个重要目标,因为它减少了漏洞暴露的风险。我们需要拥有实际发现漏洞的通知系统;通常新漏洞代表的是新发现的潜在问题,即使实际的代码并未发生改变(如存在十年之久的 Unix sudo 漏洞)。对于大型项目而言,多数这类问题将存在于间接依赖关系中。当前,虽然我们缺乏做出良好通知系统的精确度,但随着漏洞精确度和元数据的改进,我们也应该推动通知系统的改进。
截止目前,我们仅考虑了简单容易的案例:一系列全部向后兼容的升级,说明除了缺少漏洞,行为是相同的。
在实践中,升级往往并不是向后兼容的,或者被限制性版本要求所拦截。也就是说更新深入依赖关系树的程序包必须引发某些改动,或者至少是要求更新。当修复的是最新版本时常常会出现这种情况:比如最新版本是1.3,但你的软件或介于其中的程序包要求1.2。我们常常会遇到这种情况,而且这个问题仍然存在,甚至因为让所有人更新介于其中的程序包而变得更加突出。此外,如果你在1000个地方使用了一个程序包(在大型企业中并不罕见),那么你可能需要执行1000次更新。
目标:修复广泛使用的版本
修复老旧版本中的漏洞也同样重要,尤其是大量使用的老旧版本。虽然拥有长期支持的软件子集常常会这样做,但在理想情况下,所有广为使用的版本均应得到修复,尤其是安全风险的修复。
自动化发挥作用:有了某个版本的修复方案,我们可能可以为其它版本生成良好的候选修方案。虽然目前这个流程有时会手动完成,但如果我们可以让流程更容易,那么更多的版本将会得到修复,而在更高的链条中需要做的工作更少。
概言之,我们需要获得修复漏洞尤其是修复依赖关系漏洞的更轻松更及时的方法。我们需要增加广为使用的版本而非仅仅是最新版本(通常由于其中包含其它更改而难以适用)都具有修复方案的几率。
最后,还存在很多其它的“修复“选项,包括多种缓解措施如避免使用某种方法,或者限制由沙箱或访问控制带来的风险等。这些都是值得后续探讨和支持的重要的实践选项。
不管漏洞是恶意引起还是无心之过,上述框架均适用。尽管所述目标涵盖了多数漏洞,但仍不足以防御恶意行为。为了更好地进行防御,尤其是防御供应链攻击,我们需要改进开发流程。
这是一项浩大的工程,且对于多数开源软件而言并不现实。开源之美部分就在于开发流程不受限,从而吸引大量贡献者。然而,这种灵活性可能有碍安全考量。虽然我们需要贡献者,但无法寄望于所有人都能同等重视安全。相反,我们必须找出关键的程序包并加以保护。这种关键的程序包必须遵循更高的开发标准,即使这样做可能会为开发人员增加不便也在所不惜。
目标:定义值得更高标准的“关键“开源项目的准则
找到我们都依赖且其受陷则危害关键基础设施或用户隐私性的“关键“数据包至关重要。这些数据包应当遵循更高的标准,如下是其中一些标准。
如何定义“关键“并不容易,而且随着时间的流逝,定义将可能得到扩展。除了显而易见的软件如 OpenSSL 或者关键的加密库之外,还有很多广泛使用的软件但从其触及范围来讲都值得保护。我们和社区及哈佛大学经过头脑风暴启动了”关键性评分 (Criticality Score) 项目“解决开源共识问题。
目标:不对关键软件进行单方面修改
谷歌坚持的一个原则是修改不应该是单方面的,即所有改动需要至少一名作者和一名审计人员或批准人员参与。这样做的目标是限制对手能够自己执行的行为,我们需要确保有人在负责查看所做的修改。在开源中做到这一点要比在一家公司做到更难,我们需要强验证并执行代码审计和其它检查。
避免做出单方面修改可以分解为两个子目标:
目标:对关键软件开展代码审计
代码审计不仅是改进代码质量的良好流程,它还能确保至少除作者以外的一名人员会查看所有的改动。代码审计是谷歌内部执行的标准实践。
目标:修改关键软件需要获得两个独立方的批准
为了真正地实现“有人在查看“的目标,我们需要审计人员独立于贡献者。对于关键改动,我们很可能需要执行一次以上的独立审计工作。虽然我们需要筛选出可算作”独立“审计的工作,但在多数行业,独立理念本身就是审计之根本。
目标:对关键软件的参与人员进行验证
独立也意味着你了解行动者:匿名行动者不能视作独立或者可信任。目前,虽然我们基本都使用昵称:同一名人员反复使用同一个身份以便获得声誉,但我们并不能够一直了解这名人员的可信任度。这就引出一系列子目标:
目标:在关键软件中,所有人和维护人不能匿名
攻击者喜欢拥有匿名身份。在过去发生的一些供应链攻击中,攻击者利用匿名身份通过程序包社区变身为维护人员,而其他人并未意识到这个“新的维护人“具有恶意意图(受陷源代码最终被注入上游)。为缓解这种风险,我们认为应该禁止关键软件的所有人和维护人匿名。
不同于所有人和维护人,贡献者可以是匿名,但前提必须是贡献者的代码通过了得受信任方的多次审计。
同时我们可以拥有“已验证“身份,受信任实体知道真实身份,但出于隐私原因,公众并不知晓。这有助于独立性和非法行为的判断决策。
目标:关键软件贡献者的强验证
由于恶意人员寻找容易的攻击向量,因此钓鱼攻击和其它与凭据盗取的攻击很常见。我们能做的改进之一是要求使用双因素认证,尤其是所有人和维护人需使用。
目标:身份的联合模型
为了延续开源的包容性本质,虽然我们需要信任大量身份,但仍然需要具备经验证的完整性。这就要求身份的联合模型,可能类似于当前所支持的联合 SSL 证书:多个组织可以生成有效证书,但需要密切审计和互相监督。
目标:风险变化的通知系统
通知系统应该还涵盖风险变化。最显而易见的是所有权变更,它可能是新型攻击活动的前奏(如最近发生的 NPM event-stream 遭攻陷事件)。其它例子还包括发现被盗凭据、碰撞或其它恶意行为。
目标:工件的透明度
使用安全的哈希检测工件是否完整,使用数字签名证明真实性都是常见做法。增加“透明度“意味着这些证实和意图均被公开记录。这样,外部实体可以监控虚假版本的日志,即使用户并未意识到也不例外。更进一步,凭据被盗时,我们可以了解这些凭据签名了哪些工件并将其删除。这种透明度(包括可靠的公开日志和第三方监控)在 SSL 证书方面获得巨大成功,而我们也为程序包管理人员提供了这样一种方法。了解自己拥有证据的程序包或二进制就相当于了解自己访问的是真正的网站版本。
目标:信任构建流程
图灵奖获得者 Ken Thompson 在1984年所发表的著名演讲中提到,真实的源代码本身是不够的,而最近发生的一系列事件也证明这种攻击是真实存在的威胁。你如何信任自己的构建系统?必须通过持续的信任构建流程来信任并验证该构建系统的所有组件。
可复现构建虽然发挥作用(由于构建具有确定性结果,因此我们可以验证我们的构建是正确的),但由于发布工件中会出现临时数据(如时间戳),因此实现起来更加困难。安全的可复现构建要求具备验证工具,而验证工具本身必须是通过可验证和可复现的方式构建等等。我们必须构建受信任工具和构建产品的网络。
我们可以通过上述提到的透明度流程的变体“二进制授权“,经由”授权“来建立对工件和工具的信任。谷歌内部的构建系统签名所有的工件并生成和源代码绑定的显示(”manifest”)。对于开源软件而言,一个或多个受信任代理可以以服务形式运行构建,签名工件以证明这些代理为完整性负责。这种生态系统应该存在而且多数需要意识到而且需要关于证实形式的共识,以便我们可以安全地自动化这些流程。
虽然本节中的操作对于所有软件都是通用的,而且也用于谷歌公司,但是对于开源软件而言具有更重要的作用。我们希望通过关注关键软件子集,至少为该子集实现这些目标。随着工具集和自动化的发展,这些目标将更易于广泛采用。
开源软件的本质要求我们通过共识和协作解决问题。对于复杂主题如漏洞等,它意味着围绕关键问题进行焦点讨论。我们展示的是将讨论框架化的一种方式,并且定义了希望能够加速行业探讨和达成最终解决方案的一系列目标。第一系列目标广泛适用于各种漏洞且和赋能自动化以及减少风险与苦干有关。
然而,仅靠这些目标应对攻击者或防御“供应链”攻击是不够的。因此,我们提出了第二个和关键软件相关的目标。虽然第二系列目标更为艰巨,因此将会遇到一些阻力,但我们认为额外的限制对于安全而言起着根本作用。这些目标旨在集中定义“关键”软件包集,并仅对这些软件包集应用这些更高的标准。
尽管我们在如何满足这些目标方面有不同的看法,但我们在共识和可持续解决方案最重要方面意见一致。我们希望这次探讨能够促成最佳理念,并最终促成这样一种解决方案:它能够增强并梳理我们所有人都依赖的开源软件的安全。
开源软件漏洞安全风险分析
被后爹坑:开源 JavaScript 库沦为摇钱树
https://security.googleblog.com/2021/02/know-prevent-fix-framework-for-shifting.html
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。